/* Copyright (c) 2010, David Kreuter. See LICENSE for more information */

#include "headers.h"

#define ALLOC_LIST(t) m->t=(mdl_##t*)calloc(sizeof(mdl_##t),m->num_##t##s);

int process3d(uint8 *buf, uint32 len, mdl *m, mdl_layer *l){
	START(process3d);
	uint8 mode=0;
	uint32 i=0,phase=0,arg=0;
	sint32 x,y,z;

	mdl_vert tmpv;
	mdl_tri  strip_cache; // This is the cache for triangle- AND quad-strips
	tmpv.pos[0]=tmpv.pos[1]=tmpv.pos[2]=0.0f;
	tmpv.col[0]=tmpv.col[1]=tmpv.col[2]=1.0f;

	l->num_tris=l->num_quads=0;
	while(i<len){			//TODO: REUSE CODE
		uint8 j,c[4];
		for(j=0;j<4;j++)c[j]=buf[i++];
		for(j=0;j<4;j++){
			//DEBUG("3D-cmd.: 0x%2x\n",c[j]);
			switch(c[j]){
			case 0x00: break;
			//case 0x14: i+=4; break;
			//case 0x1B: i+=12; break;
			case 0x20: arg=RL(i);i+=4;
				tmpv.col[0]=(float)((arg>> 0)&31)/31.0f;
				tmpv.col[1]=(float)((arg>> 5)&31)/31.0f;
				tmpv.col[2]=(float)((arg>>10)&31)/31.0f;
				break;
			case 0x21: i+=4; break;
			case 0x22:{ sint16 s,t;
				s=RS(i);i+=2;
				t=RS(i);i+=2;
				tmpv.uv[0]=((float)s)/16.0f;
				tmpv.uv[1]=((float)t)/16.0f;
				break;}
			case 0x23:
				x=RS(i);i+=2;
				y=RS(i);i+=2;
				z=RS(i);i+=2;
				arg=RS(i);i+=2;
				if(x&0x8000)x|=0xFFFF0000;
				if(y&0x8000)y|=0xFFFF0000;
				if(z&0x8000)z|=0xFFFF0000;
				tmpv.pos[0]=((float)x)/4096.0f;
				tmpv.pos[1]=((float)y)/4096.0f;
				tmpv.pos[2]=((float)z)/4096.0f;
				goto addv;
			case 0x24: arg=RL(i);i+=4;
				x= arg     &0x3FF; if(x&0x200)x|=0xFFFFFC00;
				y=(arg>>10)&0x3FF; if(y&0x200)y|=0xFFFFFC00;
				z=(arg>>20)&0x3FF; if(z&0x200)z|=0xFFFFFC00;
				tmpv.pos[0]=((float)x)/64.0f;
				tmpv.pos[1]=((float)y)/64.0f;
				tmpv.pos[2]=((float)z)/64.0f;
				goto addv;
			case 0x25:
				x=RS(i);i+=2;
				y=RS(i);i+=2;
				if(x&0x8000)x|=0xFFFF0000; //maybe replace this
				if(y&0x8000)y|=0xFFFF0000; //by (sint32)(sint16)
				tmpv.pos[0]=((float)x)/4096.0f;
				tmpv.pos[1]=((float)y)/4096.0f;
				goto addv;
			case 0x26:
				x=RS(i);i+=2;
				z=RS(i);i+=2;
				if(x&0x8000)x|=0xFFFF0000;
				if(z&0x8000)z|=0xFFFF0000;
				tmpv.pos[0]=((float)x)/4096.0f;
				tmpv.pos[2]=((float)z)/4096.0f;
				goto addv;
			case 0x27:
				y=RS(i);i+=2;
				z=RS(i);i+=2;
				if(y&0x8000)y|=0xFFFF0000;
				if(z&0x8000)z|=0xFFFF0000;
				tmpv.pos[1]=((float)y)/4096.0f;
				tmpv.pos[2]=((float)z)/4096.0f;
				goto addv;
			case 0x28: arg=RL(i);i+=4;
				x=(arg>> 0)&0x3FF;
				y=(arg>>10)&0x3FF;
				z=(arg>>20)&0x3FF;
				if(x&0x200)x|=0xFFFFFC00;
				if(y&0x200)y|=0xFFFFFC00;
				if(z&0x200)z|=0xFFFFFC00;
				tmpv.pos[0]+=((float)x)/4096.0f;
				tmpv.pos[1]+=((float)y)/4096.0f;
				tmpv.pos[2]+=((float)z)/4096.0f;
				goto addv;
			addv: {
				#define cpyv(a,b) memcpy(a,b,sizeof(mdl_vert))
				mdl_vert *v;
				if(mode&1)
					v=m->quad[l->quad_offset+l->num_quads];
				else
					v=m->tri [l-> tri_offset+l->num_tris ];
				if(mode<2){
					uint8 ux=3+(mode&1),ix=phase%ux;
					cpyv(v+ix,&tmpv);
					if(ix==ux-1){
						if(mode&1)l->num_quads++;
						else      l->num_tris ++;
					}
				}else{
					if(phase==0)
						cpyv(strip_cache+0,&tmpv);
					else if(phase==1)
						cpyv(strip_cache+1,&tmpv);
					else if(!(phase&1)&&(mode&1))
						cpyv(strip_cache+2,&tmpv);
					else if(mode&1){ // quad strip
						cpyv(v+0,strip_cache+0);
						cpyv(v+1,strip_cache+1);
						cpyv(v+3,strip_cache+2);
						cpyv(v+2,&tmpv        );
						cpyv(strip_cache+0,v+3);
						cpyv(strip_cache+1,v+2);
						l->num_quads++;
					}else{ // tri strip
						if(phase&1){
							cpyv(v+0,strip_cache+0);
							cpyv(v+2,strip_cache+1);
							cpyv(v+1,&tmpv        );
							cpyv(strip_cache+0,v+2);
							cpyv(strip_cache+1,v+1);
						}else{
							cpyv(v+0,strip_cache+0);
							cpyv(v+1,strip_cache+1);
							cpyv(v+2,&tmpv        );
							cpyv(strip_cache+0,v+1);
							cpyv(strip_cache+1,v+2);
						}

						l->num_tris++;
					}
					/*DEBUG("Vertex: %f %f %f\n",
						tmpv.pos[0],
						tmpv.pos[1],
						tmpv.pos[2]);*/
				}

				phase++;
				break;}
			case 0x40:
				mode=RL(i);i+=4;
				phase=0;
				break;
			case 0x41:
				break;
			default:
				ERROR("Unknow BMD0-Command: 0x%2x\n",c[j]);
				goto process3d_error;
			}
		}
	}
	END(process3d,1);

	return 0;
}

mdl *read_mdl(uint8 *buf, uint32 len){
	START(read_mdl);
	mdl *m=(mdl*)malloc(sizeof(mdl));	
	m->tri  =(mdl_tri  *)0;
	m->quad =(mdl_quad *)0;
	m->layer=(mdl_layer*)0;

	MAGIC(0,MDL0,read_mdl);
	CHECK_LEN(4,read_mdl);
	m->len=len; // See .h

	uint8 num=buf[9];
	DEBUG("Number of Models: %d\n",num);
	if(num<1){
		ERROR("There are no models in the MDL-Block!\n");
		goto read_mdl_error;
	}
	if(num>1){
		ERROR("There are too many models in the MDL-Block!\n");
		goto read_mdl_error;
	}

	uint32 model_offset=RL(28);

	memcpy(m->name, buf+32, 16);
	m->name[16]=0;
	DEBUG("This model is called '%s'\n", m->name);

	uint32  code_offset=RL( 4+model_offset)+model_offset;
	uint32   mat_offset=RL( 8+model_offset)+model_offset;
	uint32 layer_offset=RL(12+model_offset)+model_offset;
	uint32   obj_offset=25+39+model_offset;
	uint32   tex_offset=RS( 0+  mat_offset)+  mat_offset;
	uint32   pal_offset=RS( 2+  mat_offset)+  mat_offset;

	m->num_mats  =buf[24+model_offset];
	m->num_layers=buf[25+model_offset];
	m->num_tris  =RS (40+model_offset);
	m->num_quads =RS (42+model_offset);
	m->num_objs  =buf[ 1+obj_offset];
	m->num_texs  =buf[ 1+tex_offset];
	m->num_pals  =buf[ 1+pal_offset];

	m->scale=((float)RL(28+model_offset))/0x100000;
	//hexdump(buf+44+model_offset,12,"Boundingbox");

	DEBUG("Materials: %d Textures: %d Palletes: %d\n",
		m->num_mats,m->num_texs,m->num_pals);
	DEBUG("Layers: %d Tris: %d Quads: %d Objects: %d\n",
		m->num_layers,m->num_tris,m->num_quads,m->num_objs);

	ALLOC_LIST(mat);
	ALLOC_LIST(tex);
	ALLOC_LIST(pal);
	ALLOC_LIST(layer);
	ALLOC_LIST(tri);
	ALLOC_LIST(quad);
	ALLOC_LIST(obj);

	DEBUG("Materials\n");ident++;
	mat_offset+=4; // Removing the 4 bytes at the end of the loop again
	for(uint32 i=0;i<m->num_mats;i++){
		INIT_OFF_NO_NAME(mdl,m,mat,mat,mt);
		ident++;

		uint32 sublen=buf[mt->offset+0x13]?0x34:0x2c;
		mt->repeat   =buf[mt->offset+0x12];
		mt->gl_id    =0;
		DEBUG("Len: %2x Repeat: %2x\n",sublen,mt->repeat);

		//hexdump(buf+mt->offset,sublen,"mdl_mat");

		ident--;
	}
	mat_offset-=4;
	ident--;

	DEBUG("Textures\n");ident++;
	for(uint32 i=0;i<m->num_texs;i++){  // TODO: Reuse code
		INIT_OFF_AND_NAME(mdl,m,tex,mat,t);
		ident++;

		uint8 num_tex_mats=t->offset>>16;
		t->offset&=0xFFFF;

		for(uint8 j=0;j<num_tex_mats;j++){
			uint8 mat_id=buf[t->offset+j];
			m->mat[mat_id].tex_id=i;
			DEBUG("Mapped to material %d\n",mat_id);
		}

		ident--;
	}
	ident--;

	DEBUG("Palletes\n");ident++;
	for(uint32 i=0;i<m->num_pals;i++){
		INIT_OFF_AND_NAME(mdl,m,pal,mat,p);
		ident++;

		uint8 num_pal_mats=p->offset>>16;
		p->offset&=0xFFFF;

		for(uint8 j=0;j<num_pal_mats;j++){
			uint8 mat_id=buf[p->offset+j];
			m->mat[mat_id].pal_id=i;
			DEBUG("Mapped to material %d\n",mat_id);
		}

		ident--;
	}
	ident--;

	DEBUG("Objects\n");ident++;
	for(uint32 i=0;i<m->num_objs;i++){
		INIT_OFF_AND_NAME(mdl,m,obj,obj,o);
		ident++;
		uint32 code=RL(o->offset);
		if(code!=0x1000f807){
			uint32 code=RL(o->offset);
			uint8 cur=o->offset+0x4;
			DEBUG("Code: %8x\n",code);
			o->srt=code;
			if(!(code&1)){
				o->translate[0]=RL(cur  );
				o->translate[1]=RL(cur+4);
				o->translate[2]=RL(cur+8);
				DEBUG("\tX: %8x\n",RL(cur  ));
				DEBUG("\tY: %8x\n",RL(cur+4));
				DEBUG("\tZ: %8x\n",RL(cur+8));
				cur+=0xC;
			}
			if(!(code&4)){
				DEBUG("TODO: Scaling!\n");
			}
			if(code&0x8){
				o->rot[0]=RL(cur  );
				o->rot[1]=RL(cur+4);
				DEBUG("\tPivot: %2x\n",(code>>4)&0x0f);
				DEBUG("\tNeg:   %2x\n",(code>>8)&0x0f);
				DEBUG("\tA: %f\n",(float)RS(cur  )/4096);
				DEBUG("\tB: %f\n",(float)RS(cur+2)/4096);
			}
		}
		ident--;
	}
	ident--;

	uint16 tri_index=0;
	uint16 quad_index=0;

	DEBUG("Layers\n");ident++;
	for(uint32 i=0;i<m->num_layers;i++){
		INIT_OFF_AND_NAME(mdl,m,layer,layer,l);
		ident++;

		l->mat_id=-1;
		// hexdump(buf+l->offset,8,"layer-bytes");
		uint32 poly_pos=RL(l->offset+8)+l->offset;
		uint32 poly_len=RL(l->offset+12);
		DEBUG("Size of poly-buffer: %d\n", poly_len);

		l->tri_offset=tri_index;   // This is not a byte- but an index-
		l->quad_offset=quad_index; // offset. (of mdl->tri or mdl->quad)
		if(!process3d(buf+poly_pos,poly_len,m,l)){
			ERROR("Processing of 3D-Data failed!\n");
			ident-=2;
			goto read_mdl_error;
		}
		tri_index +=l->num_tris;
		quad_index+=l->num_quads;

		ident--;
	}
	ident--;

	for(uint32 i=code_offset;i<mat_offset;){
		//DEBUG("Code-cmd.: %2x\n",buf[i]);
		switch(buf[i]){
			case 0x0b: case 0x2b:                       i+=1; break;
			case 0x03:                                  i+=2; break;
			case 0x02:                                  i+=3; break;
			case 0x04: case 0x24: case 0x44:
				m->layer[buf[i+3]].mat_id=buf[i+1];
			case 0x06:                                  i+=4; break;
			case 0x26: case 0x46:                       i+=5; break;
			case 0x66:                                  i+=6; break;
			case 0x01: // = End-cmd. Fulfill loop-cond. to quit it
				i=mat_offset;break;
			default: DEBUG("Unknown code!\n"); goto read_mdl_error;
		}
	}

	END(read_mdl,m);

	free_mdl(m);
	return 0;
}

int write_mdl(mdl *m, uint8 *buf, uint32 len){
	START(write_mdl);

	RL(0x00)=MDL0;
	RL(0x04)=len;
	RS(0x08)=0x0100;
	RS(0x0A)=16+8+16*m->num_objs;
	RS(0x0C)=8;
	RS(0x0E)=16;
	RL(0x10)=0x017F;
	RL(20)=0x0001002E; // first unknown
	RS(24)=4;
	RS(26)=8;
	RL(28)=48;
	memcpy(buf+0x20,m->name,16);
	/* ... */
	buf[0x5f-24]=m->num_objs;
	buf[0x60-24]=m->num_mats;
	buf[0x61-24]=m->num_layers;
	/* ... */
	RS(0x70-24)=m->num_tris;
	/* ... */
	RS(0x72-24)=m->num_quads;
	/* ... */
	RL(0x80-24)=0;
	RL(0x84-24)=0;

	uint32 obj_offset=0x88-24;
	buf[obj_offset+0x0]=0;
	buf[obj_offset+0x1]=m->num_objs;
	RS (obj_offset+0x2)=0x28; // WTF?! Should be 4! ...
	RS (obj_offset+0x4)=8;
	RS (obj_offset+0x6)=12+4*m->num_objs; // Should be 8+4*m->num_objs
	RL (obj_offset+0x8)=0x017F;
	for(uint32 i=0;i<m->num_objs;i++){
		RL(obj_offset+0xC+4*i)=0xFFEEEEFF;
	}

	uint32 data_offset=obj_offset+0xC+4*m->num_objs;
	RS(data_offset+0x0)=4;
	RS(data_offset+0x2)=4+4*m->num_objs;

	uint32 name_offset=data_offset+0x4+4*m->num_objs;
	for(uint32 i=0;i<m->num_objs;i++){
		memcpy(buf+name_offset+16*i,m->obj[i].name,16);
	}

	uint32 def_offset=name_offset+16*m->num_objs;
	for(uint32 i=0;i<m->num_objs;i++){
		mdl_obj *o=m->obj+i;
		RL(data_offset+4+4*i)=((def_offset-obj_offset)&(0x00FFFFFF))|0x77000000;
		uint8 srt=o->srt;
		buf[def_offset++]=srt;
		if(srt&1){
			RL(def_offset+=4)=o->translate[0];
			RL(def_offset+=4)=o->translate[1];
			RL(def_offset+=4)=o->translate[2];
		}
		if(srt&2){
			RL(def_offset+=4)=o->rot[0];
			RL(def_offset+=4)=o->rot[1];
		}

	}

	END(write_mdl,1);
}

void free_mdl(mdl *m){
	START(free_mdl);
	if(m){
		if(m->tri)free(m->tri);
		if(m->quad)free(m->quad);
		if(m->layer)free(m->layer);
		if(m->obj)free(m->obj);
		if(m->tex)free(m->tex);
		if(m->pal)free(m->pal);
		if(m->mat)free(m->mat);
		free(m);
	}
	ENDV(free_mdl);
}

uint32 size_mdl(mdl *m){
	START(size_mdl);
	END(size_mdl,m->len); // See .h
}
